package dispatcher.person.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.net.InetAddress;
import java.util.Random;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.Spring;
import javax.swing.SpringLayout;

import state.Globals;
import state.group.ElevatorStruct;
import state.group.HallCall;
import state.person.Person;
import state.person.PersonType;
import system.Constant;

import dispatcher.group.gui.listener.PersonDispatcherPreInitConditionListener;
import dispatcher.gui.state.GroupDispatcherStateHandler;
import dispatcher.gui.state.PersonDispatcherStateHandler;
import dispatcher.person.gui.listener.ClearPersonCreatorActionListener;
import dispatcher.person.gui.listener.CreatePersonCreatorActionListener;
import dispatcher.person.gui.network.PersonDispatcherGUIObserverListener;
import dispatcher.person.gui.network.PersonDispatcherGUIObserverThread;
import dispatcher.person.gui.listener.UpdatePersonInfoListener;

/**
 * This class is the main view for the person dispatcher.
 * 
 * The main function is similar to the group dispatcher view, where it just
 * waits for messages to be passed to it. The PersonDipatcherView also allows for 
 * creation of people and to be send to the PersonDispatcher for placing into the elevators 
 * @author Caleb
 *
 */
public class PersonDispatcherView extends JPanel{
	
	private static final int DEFAULT_MIN_WEIGHT = 120;
	
	private static final PersonDispatcherView instance = new PersonDispatcherView();
	
	public static PersonDispatcherView getInstance(){
		return instance;
	}
	
	/**
	 * 
	 */
	private static final long serialVersionUID = -8312627918799421588L;

	
	
	private JComboBox typeBox;

	private JComboBox sourceBox;

	private JComboBox destBox;
	private JTextField nameField;
	
	private PersonDispatcherGUIObserverThread networkHandler;
	private JButton createButton;
	
	private JTextArea personArea;
	
	/**
	 * The main constructor
	 */
	public PersonDispatcherView(){
		this.setLayout(new BorderLayout());
		
		createTitleText();
		createCenterPanel();
		
		GroupDispatcherStateHandler.getInstance().addListener(new PersonDispatcherPreInitConditionListener());
		PersonDispatcherStateHandler.getInstance().addListener(new UpdatePersonInfoListener());
	}
	
	/**
	 * This method will terminate all network handlers and the logger thread
	 */
	public void terminate(){
		if (networkHandler != null && networkHandler.getRemoteSocket() != null){
			
			networkHandler.sendTerminationMessage();
			
			networkHandler.shutdownThread();
		}
		if (networkHandler != null){
			try {
				networkHandler.join();
			} catch (InterruptedException e) {
				Globals.getLogger().error("Network handler could not join", e);
			}
		}
	}	
	
	/**
	 * This method will reconnect to the server
	 */
	public void reconnectToServer(InetAddress address, int port){
		// Shuts down the current socket
		if (networkHandler != null && networkHandler.getRemoteSocket() != null){
			networkHandler.shutdownThread();
		}
		// Reconnect to the server
		if (networkHandler != null){
			//networkHandler.reconnectToServer();
			if (address != null){
				resetPersonTextArea();
				connectToServer(address, port);
			}
		}
		resetGUIState();
	}
	
	private void resetGUIState(){
		// Loads the source and dest combo boxes with the correct number of selections
		String sourceDest[] = PersonDispatcherViewComboBoxHelper.getFloorSelection();
		
		sourceBox.removeAllItems();
		destBox.removeAllItems();
		
		for(int i = 0; i < sourceDest.length; i++){
			sourceBox.addItem(sourceDest[i]);
			destBox.addItem(sourceDest[i]);
		}
	}
	
	/**
	 * This method returns the network handler
	 * @return Returns the network handler
	 */
	public PersonDispatcherGUIObserverThread getNetworkHandler(){
		return networkHandler;
	}
	
	/**
	 * This method will initialize the PersonDispatcherView
	 */
	private void initializePersonDispatcherView(){
		// Initialize the person "Create" button
		createButton.addActionListener(new CreatePersonCreatorActionListener(this));
		
		resetGUIState();
	}
	
	/**
	 * This method will connect the network handler to the PersonDispatcher server
	 * @param remoteAddr The address of the server
	 * @param remotePort The port of the server
	 */
	public void connectToServer(InetAddress remoteAddr,	int remotePort){
		networkHandler = new PersonDispatcherGUIObserverThread(remoteAddr, remotePort);
		networkHandler.addListener(new PersonDispatcherGUIObserverListener());
		networkHandler.start();

		
		initializePersonDispatcherView();
	} 
	
	/**
	 * This method will create the title of the view
	 */
	private void createTitleText(){
		JLabel text = new JLabel("Person Creator", JLabel.CENTER);
		this.add(text, BorderLayout.NORTH);
	}
	
	/**
	 *  This method create the center panel that will hold all the combo boxes and their corresponding
	 *  labels
	 */
	public void createCenterPanel(){
		JPanel containerPanel = new JPanel();
		containerPanel.setLayout(new BorderLayout());
				
		createPersonSelctionPanel(containerPanel);
		this.add(containerPanel);
	}
	
	/**
	 * This method is used to create a person object from the selections made in the 
	 * combo boxes
	 * @return Returns a create person with user specified parameters
	 */
	public synchronized Person createPerson(){
		
		String name = (nameField.getText().equals(""))?"Person" : nameField.getText();
		PersonType type = null;
		int source = -1;
		int dest = -1;
		// Checks to see if the selections are null
		if (typeBox != null && typeBox.getSelectedIndex() != -1){
			type = PersonType.getPersonTypeForId((byte) (typeBox.getSelectedIndex() + 1));
		}
		if (sourceBox != null){
			source = sourceBox.getSelectedIndex() + Constant.DEFAULT_FLOOR_START;
		}
		if (destBox != null){
			dest = destBox.getSelectedIndex() + Constant.DEFAULT_FLOOR_START;
		}
		
		// If any of the selection is null, it will null instead of a person object
		if (type == null || source == -1 || dest == -1){
			return null;
		}
		
		Random randomWeight = new Random();
		// Random weight between 120 - 250
		int weight = randomWeight.nextInt(130) + DEFAULT_MIN_WEIGHT;
		// Calculates the direction based on the dest and source picked
		byte direction = (dest > source) ? ElevatorStruct.UP : ElevatorStruct.DOWN;
		return new Person(name, type, weight, new HallCall((byte)source, direction), (byte)dest);
	}
	
	/**
	 * This method will create the person selection panel and the layout of the panel
	 * @param containerPanel The container that will contain the person selection panel
	 */

	private void createPersonSelctionPanel(JPanel containerPanel){
		JPanel personSelectionPanel = new JPanel();
		SpringLayout layout = new SpringLayout();
		
		personSelectionPanel.setLayout(layout);
		
		// Type Label
		JLabel typeLabel = new JLabel("Type:", JLabel.TRAILING);
		personSelectionPanel.add(typeLabel);
		typeBox = new JComboBox(PersonDispatcherViewComboBoxHelper.getPersonTypeSelection());
		typeLabel.setLabelFor(typeBox);
		personSelectionPanel.add(typeBox);

		// Source Label
		
		JLabel sourceLabel = new JLabel("Source:", JLabel.TRAILING);
		personSelectionPanel.add(sourceLabel);
		sourceBox = new JComboBox(PersonDispatcherViewComboBoxHelper.getFloorSelection());
		typeLabel.setLabelFor(sourceBox);
		personSelectionPanel.add(sourceBox);

		// Dest
		JLabel destLabel = new JLabel("Dest:", JLabel.TRAILING);
		personSelectionPanel.add(destLabel);
		destBox = new JComboBox(PersonDispatcherViewComboBoxHelper.getFloorSelection());
		typeLabel.setLabelFor(destBox);
		personSelectionPanel.add(destBox);
		
		// Name
		JLabel nameLabel = new JLabel("Name:", JLabel.TRAILING);
		personSelectionPanel.add(nameLabel);
		nameField = new JTextField(10);
		typeLabel.setLabelFor(nameField);
		personSelectionPanel.add(nameField);
		
		// Clear button
		JButton clearButton = new JButton("Clear");
		clearButton.addActionListener(new ClearPersonCreatorActionListener(this));
		personSelectionPanel.add(clearButton, BorderLayout.WEST);
		
		// Create button
		createButton = new JButton("Create");
		personSelectionPanel.add(createButton, BorderLayout.EAST);
		
		// Organizes the layout
		calculateBestLayout(personSelectionPanel, 5, 2);
		resetFields();
		// Addeds to main panel
		containerPanel.add(personSelectionPanel, BorderLayout.CENTER);
		containerPanel.add(createPersonTextView(), BorderLayout.EAST);
	}
	
	/**
	 * This method will calculate the best possible location for a component, so its
	 * aligned with the other components and in a grid format
	 * @param c The container to be organizedn
	 * @param row The number of rows in the grid adjustments
	 * @param column The number of columns in the grid adjustments
	 */
	private void calculateBestLayout(Container c, int row, int column){
		SpringLayout layout = (SpringLayout)c.getLayout();
		int max = row * column;
		
		// Calculates the largest width among the components in a container
		Spring maxWidth = layout.getConstraints(c.getComponent(0)).getWidth();
		Spring maxHeight = layout.getConstraints(c.getComponent(0)).getHeight();
		
		for(int i = 1; i < max; i++){
			SpringLayout.Constraints constraint = layout.getConstraints(c.getComponent(i));
			
			maxWidth = Spring.max(maxWidth, constraint.getWidth());
			maxHeight = Spring.max(maxHeight, constraint.getHeight());
		}
		
		// Goes through all the containers and set the new location of the component
		// and the width of the component
		SpringLayout.Constraints lastConstraint = null;
		SpringLayout.Constraints lastRowCons = null;
		for(int i = 0; i < max; i++){
			SpringLayout.Constraints constraints = layout.getConstraints(c.getComponent(i));
			
			// Sets the contraint of the first item in a row
			if (i % column == 0){
				lastRowCons = lastConstraint;
				constraints.setX(Spring.constant(10));
				constraints.setWidth(maxWidth);
			}else{
				constraints.setX(Spring.sum(lastConstraint.getConstraint(SpringLayout.EAST), Spring.constant(10)));
				constraints.setWidth(maxWidth);
			}
			
			// Sets the constraints for the top left most item in the grid
			if (i / column == 0){
				constraints.setY(Spring.constant(10));
				constraints.setHeight(maxHeight);
			}else{
				constraints.setY(Spring.sum(lastRowCons.getConstraint(SpringLayout.SOUTH), Spring.constant(10)));
				constraints.setHeight(maxHeight);
			}
			lastConstraint = constraints;
		}
	}
	
	private Container createPersonTextView(){
		this.personArea = new JTextArea();
		this.personArea.setEnabled(false);
		
		this.personArea.setBorder(BorderFactory.createLineBorder(Color.BLACK));
		//this.personArea.setPreferredSize(new Dimension(500, 100));
		this.personArea.setDisabledTextColor(Color.BLACK);
		
		JScrollPane textScroll = new JScrollPane(this.personArea);
		textScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
		textScroll.setPreferredSize(new Dimension(500, 100));
		return textScroll;
	}
	
	/**
	 * This method will update the person text area with the list of current people in the system
	 */
	public void updatePersonTextArea(){
		this.personArea.setText(PersonDispatcherStateHandler.getInstance().getPersonCollectionInfo());
	}
	
	/**
	 * This method will reset the people in the system
	 */
	public synchronized void resetPersonTextArea(){
		PersonDispatcherStateHandler.getInstance().resetPeople();
		this.personArea.setText("");
	}
	
	/**
	 * This method will reset the fields of all the combo boxes
	 */
	public void resetFields(){
		if (typeBox != null){
			typeBox.setSelectedIndex(-1);
		}
		if (sourceBox != null){
			sourceBox.setSelectedIndex(-1);
		}
		if (destBox != null){
			destBox.setSelectedIndex(-1);
		}
		if (nameField != null){
			nameField.setText("");
		}
	}
}
